package com.marshalchen.common.uiModule.cooldraganddrop;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import com.marshalchen.common.R;
import com.marshalchen.common.commonUtils.logUtils.Logs;

import java.util.LinkedList;
import java.util.List;

public class SpanVariableGridView extends AdapterView<BaseAdapter> {
    /**
     * If the last item can be drag or it counld be used as other function
     */
    boolean isLastItemCouldDrag = true;
    private static final int NOT_DEFINED_VALUE = -1;

    public static interface CalculateChildrenPosition {
        void onCalculatePosition(final View view, final int position, final int row, final int column);

    }

    ;

    private static final int INVALID_POSITION = NOT_DEFINED_VALUE;

    private static final int TOUCH_STATE_RESTING = 0;
    private static final int TOUCH_STATE_CLICK = 1;
    private static final int TOUCH_STATE_LONG_CLICK = 2;

    private int mTouchStartX;
    private int mTouchStartY;
    private int mTouchStartItemPosition;
    private Runnable mLongPressRunnable;

    private int mColCount = 2;
    private int mItemMargin = 0;
    protected int mControlHeight = 0;
    private Rect mRect = new Rect();
    protected boolean mPopulating = false;
    private int mTouchState = TOUCH_STATE_RESTING;

    protected BaseAdapter mAdapter = null;
    private TransitionDrawable mItemTransitionDrawable = null;
    private List<CalculateChildrenPosition> mCalculateChildrenPositionList = new LinkedList<CalculateChildrenPosition>();

    private final DataSetObserver mObserver = new DataSetObserver() {

        @Override
        public void onChanged() {
            mPopulating = false;
            removeAllViewsInLayout();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
        }
    };

    public static class LayoutParams extends AdapterView.LayoutParams {
        private static final int[] LAYOUT_ATTRS = new int[]{android.R.attr.layout_span};

        public static final int SPAN_INDEX = 0;
        public static final int ALL_COLUMNS = NOT_DEFINED_VALUE;

        public int span = 1;

        public int position = NOT_DEFINED_VALUE;

        public int row = NOT_DEFINED_VALUE;

        public int column = NOT_DEFINED_VALUE;

        long id = NOT_DEFINED_VALUE;

        public LayoutParams(int height) {
            super(MATCH_PARENT, height);

            if (this.height == MATCH_PARENT) {
                this.height = WRAP_CONTENT;
            }
        }

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            setupWidthAndHeight();

            TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
            span = a.getInteger(SPAN_INDEX, 1);
            a.recycle();
        }

        public LayoutParams(AdapterView.LayoutParams other) {
            super(other);

            setupWidthAndHeight();

        }

        private void setupWidthAndHeight() {

            if (this.width != MATCH_PARENT) {
                this.width = MATCH_PARENT;
            }

            if (this.height == MATCH_PARENT) {
                this.height = WRAP_CONTENT;
            }

        }

    }

    public SpanVariableGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        initialize(attrs);
    }

    public SpanVariableGridView(Context context, AttributeSet attrs) {
        super(context, attrs);

        initialize(attrs);

    }

    public SpanVariableGridView(Context context) {
        super(context);
    }

    private void initialize(final AttributeSet attrs) {

        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SpanVariableGridView);

            try {
                mColCount = a.getInteger(R.styleable.SpanVariableGridView_numColumns, 2);
                mItemMargin = a.getDimensionPixelSize(R.styleable.SpanVariableGridView_itemMargin, 0);

            } finally {
                a.recycle();

            }

        } else {

            mColCount = 2;
            mItemMargin = 0;
        }
    }

    public boolean addCalculateChildrenPositionListener(final CalculateChildrenPosition listener) {

        return mCalculateChildrenPositionList.add(listener);

    }

    public boolean removeCalculateChildrenPositionListener(final CalculateChildrenPosition listener) {

        return mCalculateChildrenPositionList.remove(listener);

    }

    @Override
    public BaseAdapter getAdapter() {
        return mAdapter;
    }

    @Override
    public View getSelectedView() {
        return null;
    }

    @Override
    public void setAdapter(BaseAdapter adapter) {

        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
        }

        mAdapter = adapter;

        if (mAdapter != null) {
            mAdapter.registerDataSetObserver(mObserver);
        }
        removeAllViewsInLayout();
        requestLayout();

    }

    @Override
    public void setSelection(int position) {
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        if (mAdapter == null) {
            return;
        }

        layoutChildrens(INVALID_POSITION, false);
    }

    protected void performDragAndDropSwapping(int from, int to) {

        mPopulating = true;
        Logs.d("drag----" + from + "   " + to + "   " + mAdapter.getCount());
        if (from != to && isLastItemCouldDrag ? true : to != mAdapter.getCount() - 1) {

            final View removedChild = getChildAt(from);
            removedChild.clearAnimation();

            removeViewInLayout(removedChild);
            addViewInLayout(removedChild, to, removedChild.getLayoutParams());
        }

        mControlHeight = measureChildrens(false);

        layoutChildrens(to, true);

        mPopulating = false;
    }

    @Override
    public void requestLayout() {

        if (!mPopulating) {
            super.requestLayout();
        }
    }

    protected Rect layoutChildrens(final int draggedChild, final boolean animate) {
        int row = 0;
        int rowHeight = 0;
        int fullHeight = mItemMargin;
        int width = getMeasuredWidth() - 2 * mItemMargin;
        final int colWidth = (width - (mColCount - 1) * mItemMargin) / mColCount;

        Rect draggedChildRect = null;

        for (int position = 0; position < mAdapter.getCount(); position++) {
            final View childView = getChildAt(position);

            final Point prev = new Point(childView.getLeft(), childView.getTop());

            final LayoutParams lp = (LayoutParams) childView.getLayoutParams();

            final int column = lp.column;

            if (row != lp.row) {
                fullHeight += (rowHeight + mItemMargin);
                rowHeight = 0;
            }

            rowHeight = Math.max(rowHeight, childView.getMeasuredHeight());
            row = lp.row;

            final int width_ = column == LayoutParams.ALL_COLUMNS ? width : (lp.span * (colWidth + mItemMargin) - mItemMargin);

            final int left_ = mItemMargin + (column == LayoutParams.ALL_COLUMNS ? 0 : column * (colWidth + mItemMargin));
            final int top_ = fullHeight;
            final int right_ = left_ + width_;
            final int bottom_ = top_ + childView.getMeasuredHeight();

            measureChildren(childView, width_, lp.height);

            if (position != draggedChild) {
                final Point now = new Point(left_, top_);

                childView.layout(left_, top_, right_, bottom_);

                if (animate) {
                    translateChild(childView, prev, now);
                }
            } else {
                draggedChildRect = new Rect(left_, top_, right_, bottom_);

            }
        }

        return draggedChildRect;
    }

    protected final void translateChild(View v, Point prev, Point now) {

        TranslateAnimation translate = new TranslateAnimation(Animation.ABSOLUTE, -now.x + prev.x, Animation.ABSOLUTE, 0, Animation.ABSOLUTE, -now.y
                + prev.y, Animation.ABSOLUTE, 0);
        translate.setInterpolator(new AccelerateInterpolator(4f));
        translate.setDuration(350);
        translate.setFillEnabled(false);
        translate.setFillAfter(false);

        v.clearAnimation();
        v.startAnimation(translate);
    }

    protected void measureChildren(View child, final int width, final int height) {

        final int heightSpec;
        if (height == LayoutParams.WRAP_CONTENT) {
            heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        } else {
            heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        }

        final int widthSpec;
        if (width == LayoutParams.WRAP_CONTENT) {
            widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        } else {
            widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        }

        child.measure(widthSpec, heightSpec);
    }

    protected int pointToPosition(final int draggedChild, final int x, final int y) {

        for (int index = 0; index < getChildCount(); index++) {

            if (index == draggedChild) {
                continue;
            }

            getChildAt(index).getHitRect(mRect);

            if (mRect.contains(x, y)) {
                return index;
            }
        }
        return INVALID_POSITION;
    }

    private void clickChildAt() {

        final int index = pointToPosition(INVALID_POSITION, mTouchStartX, mTouchStartY);

        if (index != INVALID_POSITION && index == mTouchStartItemPosition) {

            final View itemView = getChildAt(index);
            final int position = index;
            final long id = mAdapter.getItemId(position);
            performItemClick(itemView, position, id);

        }
    }

    private void longClickChild(final int index) {

        final View itemView = getChildAt(index);
        final long id = mAdapter.getItemId(index);
        final OnItemLongClickListener listener = getOnItemLongClickListener();

        if (listener != null) {
            listener.onItemLongClick(this, itemView, index, id);
        }
    }

    private void startLongPressCheck() {

        if (mLongPressRunnable == null) {
            mLongPressRunnable = new Runnable() {
                public void run() {

                    if (mTouchState == TOUCH_STATE_CLICK) {
                        final int index = pointToPosition(INVALID_POSITION, mTouchStartX, mTouchStartY);
                        if (index != INVALID_POSITION && index == mTouchStartItemPosition) {
                            longClickChild(index);
                            mTouchState = TOUCH_STATE_LONG_CLICK;
                        }
                    }
                }
            };
        }

        postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());
    }

    protected void startLongClickTransition(final View clickedChild) {

        if (clickedChild != null && mItemTransitionDrawable == null) {

            if (clickedChild.getBackground().getCurrent() instanceof TransitionDrawable) {

                mItemTransitionDrawable = (TransitionDrawable) clickedChild.getBackground().getCurrent();

                mItemTransitionDrawable.startTransition(ViewConfiguration.getLongPressTimeout());
            }
        }
    }

    protected void resetLongClickTransition() {

        if (mItemTransitionDrawable != null) {

            mItemTransitionDrawable.resetTransition();
            mItemTransitionDrawable = null;

        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        if (isInEditMode() || mAdapter == null) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        mControlHeight = measureChildrens(false);

        final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(measuredWidth, mControlHeight);
    }

    private void fireCalculateChildrenPositionEvent(final View view, final int position, final int row, final int column) {

        for (CalculateChildrenPosition listener : mCalculateChildrenPositionList) {
            listener.onCalculatePosition(view, position, row, column);
        }

    }

    protected int measureChildrens(final boolean justMeasure) {

        int row = 0;
        int col = 0;
        int rowHeight = 0;
        int spansFilled = 0;
        int fullHeight = mItemMargin;

        for (int position = 0; position < mAdapter.getCount(); position++) {
            View childView = getChildAt(position);

            if (childView == null) {
                childView = mAdapter.getView(position, null, this);

                LayoutParams params = (LayoutParams) childView.getLayoutParams();
                if (params == null) {

                    params = new LayoutParams(new AdapterView.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                }

                if (!justMeasure) {
                    addViewInLayout(childView, NOT_DEFINED_VALUE, params);
                }
            }

            final LayoutParams lp = (LayoutParams) childView.getLayoutParams();

            measureChildren(childView, lp.width, lp.height);

            lp.position = position;
            spansFilled += lp.span;

            while (true) {

                if (spansFilled <= mColCount) {

                    lp.row = row;
                    lp.column = lp.span == mColCount ? LayoutParams.ALL_COLUMNS : col;
                    col = spansFilled;

                    if (justMeasure) {
                        fireCalculateChildrenPositionEvent(childView, lp.position, lp.row, lp.column);
                    } else {
                        childView.setLayoutParams(lp);
                    }

                    rowHeight = Math.max(rowHeight, mItemMargin + childView.getMeasuredHeight());
                }

                if (spansFilled >= mColCount) {

                    fullHeight += rowHeight;
                    row++;

                    col = 0;
                    rowHeight = 0;

                    if (spansFilled != mColCount) {

                        spansFilled = lp.span;
                        continue;
                    }

                    spansFilled = 0;
                }

                break;
            }
        }

        fullHeight += rowHeight;
        return fullHeight;
    }

    public void requestCalculateChildrenPositions() {

        measureChildrens(true);

    }

    private void startTouch(final MotionEvent event) {

        mTouchStartX = (int) event.getX();
        mTouchStartY = (int) event.getY();
        mTouchStartItemPosition = pointToPosition(INVALID_POSITION, mTouchStartX, mTouchStartY);

        startLongPressCheck();

        mTouchState = TOUCH_STATE_CLICK;
    }

    @Override
    public void childDrawableStateChanged(View child) {

        startLongClickTransition(child);

        super.childDrawableStateChanged(child);
    }

    private void endTouch() {

        resetLongClickTransition();

        removeCallbacks(mLongPressRunnable);

        mTouchState = TOUCH_STATE_RESTING;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        final boolean result = super.dispatchTouchEvent(event);

        if (getChildCount() == 0) {
            return result;
        }

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                startTouch(event);
                break;

            case MotionEvent.ACTION_MOVE:
                break;

            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_CLICK) {
                    clickChildAt();
                }

                endTouch();
                break;

            default:
                endTouch();
                break;
        }

        return result;
    }

}
